package jmind.core.http;


import jmind.base.lang.Pair;
import jmind.base.util.AppEnvironment;
import jmind.base.util.DataUtil;
import jmind.base.util.GlobalConstants;
import jmind.base.util.reflect.ClassUtil;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.nio.conn.NHttpClientConnectionManager;
import org.apache.http.nio.conn.NoopIOSessionStrategy;
import org.apache.http.nio.conn.SchemeIOSessionStrategy;
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
import org.apache.http.nio.reactor.ConnectingIOReactor;
import org.apache.http.nio.reactor.IOReactorException;
import org.apache.http.nio.reactor.ssl.SSLIOSession;
import org.apache.http.util.EntityUtils;


import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;


import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * @Author: xieweibo
 * @Date: 2018/4/3
 * https://blog.csdn.net/a63881763/article/details/78583475?locationNum=3&fps=1
 * https://github.com/a63881763/HttpAsyncClientUtils
 */
public class PoolingHttpAsyncClient  extends  HttpClient4{
    public static void main(String[] args) {
        // String post = HttpUtil.post("https://pre-drug.mashangfangxin.com/", null);
        String post = ClassUtil.getProtectionDomain(SSLIOSession.class);
        System.err.println(post);
    }

//    public static void main(String[] args) {
//        try {
//            PoolingHttpAsyncClient client=new PoolingHttpAsyncClient();
//            client.postFuture("https://isv.mashangfangxin.com/getjson",GlobalConstants.UTF8,null,new FutureCallback<HttpResponse>() {
//                @Override
//                public void completed(HttpResponse result) {
//                    try {
//                        HttpEntity entity = result.getEntity();
//                        System.err.println("completed "+EntityUtils.toString(entity));
//                        EntityUtils.consume(entity);
//                    } catch (IOException e) {
//                        e.printStackTrace();
//                    }
//                }
//
//                @Override
//                public void failed(Exception ex) {
//                    System.err.println("failed");
//                }
//
//                @Override
//                public void cancelled() {
//                    System.err.println("cancelled");
//
//                }
//            });
//            System.err.println("ssss----"+client.post("http://isv.mashangfangxin.com/getjson",null));
//            System.err.println("ssss----"+client.post("https://isv.mashangfangxin.com/getjson",null));
//            System.err.println("ssss----"+client.post("https://isv.mashangfangxin.com/getjson",null));
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//
//    }
    private final CloseableHttpAsyncClient httpClient;

    protected PoolingHttpAsyncClient() throws IOReactorException, NoSuchAlgorithmException, KeyManagementException {

        IOReactorConfig ioReactorConfig = IOReactorConfig.custom()
                .setIoThreadCount(GlobalConstants.NCPU).build();
        ConnectingIOReactor ioReactor = new DefaultConnectingIOReactor(
                ioReactorConfig);
        //调用SSL之前取消重写验证方法，取消检测SSL
        X509TrustManager trustManager =new X509TrustManager() {


            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

            }

            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        };

        SSLContext ctx = SSLContext.getInstance(SSLConnectionSocketFactory.TLS);

        ctx.init(null, new TrustManager[]{trustManager}, null);

        SSLIOSessionStrategy strategy =new SSLIOSessionStrategy(ctx, NoopHostnameVerifier.INSTANCE);
        Registry<SchemeIOSessionStrategy> registry = RegistryBuilder.<SchemeIOSessionStrategy>create()
                .register("http", NoopIOSessionStrategy.INSTANCE).register("https", strategy).build();
        PoolingNHttpClientConnectionManager cm =
                new PoolingNHttpClientConnectionManager(ioReactor,registry);
        cm.setMaxTotal(300);
        cm.setDefaultMaxPerRoute(30);

        HttpAsyncClientBuilder httpClientBuilder = HttpAsyncClients.custom();
        httpClientBuilder.setConnectionManager(cm);




        // 链接超时setConnectTimeout ，读取超时setSocketTimeout
        int connectTimeout= DataUtil.toInt(AppEnvironment.getProperty("connectTimeout","2000"));
        int socketTimeout=DataUtil.toInt(AppEnvironment.getProperty("socketTimeout","5000"));

        RequestConfig defaultRequestConfig = RequestConfig.custom().setConnectTimeout(connectTimeout).setSocketTimeout(socketTimeout)
                .build();
        httpClientBuilder.setDefaultRequestConfig(defaultRequestConfig);
        httpClientBuilder.disableCookieManagement();
        httpClient=httpClientBuilder.build();
        /*
         * socketTimeout的值限制了closeIdleConnections执行的周期。
         * 如果周期相对socketTimeout的值过长，有可能一个请求分配到一个即将socketTimeout的连接，
         * 在请求发送之前即抛出SocketTimeoutException。
         * 现在让closeIdleConnections的执行周期为socketTimeout / 2.5。
         */
        int closePeriod = (int) (socketTimeout/2.5);
        closePeriod = closePeriod < 5000 ? closePeriod : 5000;
        httpClient.start();
        new IdleConnectionEvictor(cm, closePeriod).start();

    }



    @Override
    public String get(String url, String code, int timeout,Map<String, String> headers){
        long start = System.currentTimeMillis();
        String res=null;
        HttpEntity entity=null;
        try {
            Future<HttpResponse> future = getFuture(url,null,headers);
            if(future!=null){
                HttpResponse response = timeout > 0 ? future.get(timeout, TimeUnit.MILLISECONDS) : future.get();
                entity =response.getEntity();
                res=EntityUtils.toString(entity, code);
            }
            success(start,url,res);
        } catch (Exception e) {
            fail(start, url , e);
        }finally {
            EntityUtils.consumeQuietly(entity);
        }
        return res;
    }
    public Future<HttpResponse> getFuture(String url, FutureCallback<HttpResponse> callback,Map<String, String> headers) {
        final HttpGet httpget = new HttpGet(url);
        if(!DataUtil.isEmpty(headers)){
            headers.forEach((k,v)->{
                httpget.setHeader(k,v);
            });
        }
       // httpGet.setHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36");
        return httpClient.execute(httpget, callback);
    }

    public String post(String url, String code, int timeout, List<NameValuePair> params,Map<String,String> headers) {
        long start = System.currentTimeMillis();
        String res = null;
        HttpPost httpPost = null;
        HttpEntity entity=null;
        try {
            httpPost = createHttpPost(url, code, params, headers);
            Future<HttpResponse> future = httpClient.execute(httpPost, null);
            HttpResponse response = timeout > 0 ? future.get(timeout, TimeUnit.MILLISECONDS) : future.get();
            entity =response.getEntity();
            res = EntityUtils.toString(entity, code);
            success(start, url + "?" + params, res);
        } catch (Exception e) {
            httpPost.abort();
            fail(start, url + "?" + params, e);
        } finally {
            httpPost.releaseConnection();
            EntityUtils.consumeQuietly(entity);
        }
        return res;
    }


    public Future<HttpResponse> postFuture(String url, String code,List<NameValuePair> params,FutureCallback<HttpResponse> callback) {
        long start = System.currentTimeMillis();
        HttpPost httpPost = new HttpPost(url);
        try {
            if (params != null)
                httpPost.setEntity(new UrlEncodedFormEntity(params, code));
            Future<HttpResponse> future = httpClient.execute(httpPost, callback);
            return future;
        } catch (Exception e) {
            httpPost.abort();
            fail(start, url + "?" + params, e);
        }
        return null;
    }



    @Override
    public String postBody(String url, String body) {
        long start = System.currentTimeMillis();
        String res = null;
        HttpPost httppost = new HttpPost(url);
        try {
            if (!DataUtil.isEmpty(body)) {
                httppost.setEntity(new StringEntity(body, GlobalConstants.CHARSET_UTF8));
            }
            Future<HttpResponse> response = httpClient.execute(httppost, null);
            HttpEntity resEntity = response.get().getEntity();
            if (resEntity != null) {
                res = EntityUtils.toString(resEntity, GlobalConstants.CHARSET_UTF8);
                EntityUtils.consume(resEntity);
            }
            success(start, url, res);
        } catch (Exception e) {
            fail(start, url, e);
        } finally {
            httppost.releaseConnection();
        }
        return res;
    }

    @Override
    public String postFile(String url, Map<String, ?> params, String fileName, File file, String code) {
        Charset charset = DataUtil.isEmpty(code) ? Charset.defaultCharset() : Charset.forName(code);
        long start = System.currentTimeMillis();
        String res = null;
        HttpPost httppost = new HttpPost(url);
        try {


            //  setMode 解决文件名是中文。乱码问题
            MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create()
                    .setMode(HttpMultipartMode.BROWSER_COMPATIBLE).setCharset(charset);
            multipartEntityBuilder.addBinaryBody(fileName, file);

            if (!DataUtil.isEmpty(params)) {
                ContentType contentType = ContentType.create("text/plain", charset);
                for (Map.Entry<String, ?> entry : params.entrySet()) {
                    multipartEntityBuilder.addTextBody(entry.getKey(), entry.getValue().toString(), contentType);
                }
            }

            httppost.setEntity(multipartEntityBuilder.build());
            Future<HttpResponse> response = httpClient.execute(httppost, null);
            HttpEntity resEntity = response.get().getEntity();
            if (resEntity != null) {
                res = EntityUtils.toString(resEntity, charset);
                EntityUtils.consume(resEntity);
            }
            httppost.releaseConnection();
            success(start, url, res);
        } catch (Exception e) {
            fail(start, url, e);
        } finally {
            httppost.releaseConnection();
        }
        return res;
    }

    @Override
    public String postFile(String url, Map<String, ?> params, List<Pair<String, File>> files, String code) {
        Charset charset = DataUtil.isEmpty(code) ? Charset.defaultCharset() : Charset.forName(code);
        long start = System.currentTimeMillis();
        String res = null;
        HttpPost httppost = new HttpPost(url);
        try {


            //  setMode 解决文件名是中文。乱码问题
            MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create()
                    .setMode(HttpMultipartMode.BROWSER_COMPATIBLE).setCharset(charset);
            for (Pair<String, File> file : files) {
                multipartEntityBuilder.addBinaryBody(file.getFirst(), file.getSecond());
            }

            if (!DataUtil.isEmpty(params)) {
                ContentType contentType = ContentType.create("text/plain", charset);
                for (Map.Entry<String, ?> entry : params.entrySet()) {
                    multipartEntityBuilder.addTextBody(entry.getKey(), entry.getValue().toString(), contentType);
                }
            }

            httppost.setEntity(multipartEntityBuilder.build());
            Future<HttpResponse> response = httpClient.execute(httppost, null);
            HttpEntity resEntity = response.get().getEntity();
            if (resEntity != null) {
                res = EntityUtils.toString(resEntity, charset);
                EntityUtils.consume(resEntity);
            }
            httppost.releaseConnection();
            success(start, url, res);
        } catch (Exception e) {
            fail(start, url, e);
        } finally {
            httppost.releaseConnection();

        }
        return res;
    }


    static class IdleConnectionEvictor extends Thread {
        private final NHttpClientConnectionManager connMgr;
        private volatile boolean shutdown;
        private long closePeriod;

        public IdleConnectionEvictor(
                NHttpClientConnectionManager connMgr,
                long closePeriod)
        {
            this.connMgr = connMgr;
            this.closePeriod = closePeriod;
        }

        @Override
        public void run() {
            try {
                while (!shutdown) {
                    synchronized (this) {
                        wait(closePeriod);
                        connMgr.closeExpiredConnections();
                        connMgr.closeIdleConnections(
                                closePeriod, TimeUnit.MILLISECONDS);
                    }
                }
            } catch (InterruptedException ex) {
                // terminate
            }
        }

        public void shutdown() {
            shutdown = true;
            synchronized (this) {
                notifyAll();
            }
        }
    }
}
